
#ifndef LIST_HPP_
#define LIST_HPP_

#include "listIterator.hpp"
#include "listNode.hpp"
#include <iostream>

/**
 * Template List: lista ordenada, a la
 * que se le puede setear el modo de
 * ordenamiento, de menor a mayor (por
 * default) y de mayor a menor.
 * Para poder utilizar esta lista, la clase
 * T que instancie un objeto de tipo List<T>
 * debe implementar los operadores:
 *    <, <=, >, >=, ==, !=, =.
 */
template<class T> class List {
private:
	ListNode<T>* first;
	ListNode<T>* last;
	/**
	 * Modo de comparacion:
	 *  - comparison = 1, menor a mayor, por default
	 *  - comparison = 0, mayor a menor
	 */
	int			 comparison;
public:

	/**
	 * Constructor de la clase List<T>
	 */
	List(){
		this->first = NULL;
		this->last = NULL;
		this->comparison = 1;
	}

	/**
	 * Destructor de la clase List<T>
	 */
	virtual ~List(){
		ListIterator<T> it = this->iterator();
		while(it.hasNext()){
			it.remove();
		}
	}

	/**
	 * Metodo setter del modo de comparacion de
	 * la lista.
	 * @param c de tipo int.
	 */
	void setComparisonMode(int c){
		this->comparison = c;
	}

	/**
	 * Chequea si la lista esta vacia.
	 * @return true si la lista esta vacia.
	 * @return false en caso contrario.
	 */
	bool isEmpty(){
		return (this->first == NULL);
	}

	/**
	 * Metodo que retorna el tamaño de la lista.
	 * @return Retorna el tamaño de la lista.
	 */
	unsigned int size(){
		unsigned int elementos = 0;
		ListIterator<T> it = this->iterator();

		while (it.hasNext()) {
			elementos++;
		    it.next();
		}
		return elementos;
	}

	/**
	 * Metodo para insertar elementos de tipo T a la lista,
	 * inserta antes del elemento mayor o antes de null.
	 * @param datta de clase T*.
	 */
	void add(T *datta){
		bool insert = false;
		ListIterator<T> it = this->iterator();

		/* Inserta ordenado 
		*  - comparison = 1, menor a mayor, por default
		*  - comparison = 0, mayor a menor
		*/
		while((it.hasNext()) && (!insert)){

			if(this->comparison > 0){
				if(*(it.get()) > *datta )
					insert = true;
				else
					it.next();
			}
			else{
				if(*(it.get()) < *datta )
					insert = true;
				else
					it.next();
			}
		}

		it.add(datta);
	}

	/**
	 * Metodo para remover elementos de tipo T de la lista.
	 * @param datta de clase T*.
	 * @return Retorna el dato alojado en la lista.
	 */
	T* remove(T* datta){
		bool found = false;
		ListIterator<T> it = this->iterator();
		T* dat;

		while((it.hasNext())&&(!found)){
			if(*(it.get()) == *datta )
			{
				dat = it.remove();
				found = true;
			}
			else
				it.next();
		}

		return dat;
	}

	/**
	 * Metodo para actualizar un elemento de la lista.
	 * @param datta de tipo T*.
	 */
	void update(T* datta){
		T* dat = this->search(datta);
		if(dat != NULL){
			T* d = this->remove(dat);
			d = NULL;
		}
		this->add(datta);
	}

	/**
	 * Metodo que retorna un iterador de la lista.
	 * @see clase ListIterator<T>
	 * @return Retorna un iterador de clase ListIterator<T>.
	 */
	ListIterator<T> iterator(){
		ListIterator<T> it(this);
		return it;
	}

	/**
	 * Metodo que busca en la lista el elemento
	 * pasado por parámetro, para ser utilizado este
	 * metodo la clase T debe implementar los operadores:
	 * <, <=, >, >=, ==, != e =.
	 * @param datta de clase T*.
	 * @return el elemento alojado en la lista.
	 */
	T* search(T* datta){
		bool found = false;
		ListIterator<T> it = this->iterator();
		while((it.hasNext())&&(!found)){

			if(*(it.get()) == *datta ){
				found = true;
			}
			else{
				it.next();
			}
		}

		if(found)
			return it.get();
		else
			return NULL;
	}

	/**
	 * Operador comparacion.
	 */
	bool operator==(List<T> &original){
		ListIterator<T> currentIt = this->iterator();
		ListIterator<T> originalIt = original.iterator();

		while (*(currentIt.get()) == *(originalIt.get())){
			if(currentIt.hasNext()){
				currentIt.next();
				if(originalIt.hasNext()){
					originalIt.next();
				}
				else
					return false;
			}
			else if(originalIt.hasNext()){
					return false;
				return true;
			}
		}

		return true;
	}

	/**
	 * Operador asignacion.
	 */
	List<T>& operator=(List<T> &original){
		this->first = original.getFirst();
		this->last = original.getLast();
		return *this;
	}

	/**
	 * Metodo que devuelve el primer elemento de la
	 * lista, removiendolo de la misma.
	 * @return Retorna el primer elemento alojado en
	 * la lista.
	 */
	T* popFirst(){
		if(this->first != NULL){
			T* datta = this->first->getDatta();
			ListNode<T> *temp = this->first;
			this->first = this->first->getNext();
			delete temp;
			return datta;
		}
		else
			return NULL;
	}

private:

	/**
	 * Metodo getter del primer elemento-de-lista de la lista.
	 * @scope private.
	 * @return Retorna un puntero al primer elemento-de-lista.
	 */
	ListNode<T>* getFirst(){
		return this->first;
	}

	/**
	 * Metodo setter del primero elemento-de-lista de la lista.
	 * @scope private.
	 * @param f de clase ListNode<T>*.
	 */
	void setFirst(ListNode<T>* f){
		this->first = f;
	}

	/**
	 * Metodo getter del ultimo elemento-de-lista de la lista.
	 * @scope private.
	 * @return Retorna un puntero al ultimo elemento-de-lista.
	 */
	ListNode<T>* getLast(){
		return this->last;
	}

	/**
	 * Metodo setter del ultimo elemento-de-lista de la lista.
	 * @scope private.
	 * @param l de clase ListNode<T>*.
	 */
	void setLast(ListNode<T>* l){
		this->last = l;
	}

	/**
	 * Declaracion de clase amiga de la lista.
	 */
	friend class ListIterator<T>;
};


#endif /* LIST_HPP_ */

